package ssi.peeno.ciphers;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.security.*;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.*;

import javax.crypto.Cipher;
import javax.crypto.CipherOutputStream;
import javax.crypto.NoSuchPaddingException;

import org.apache.commons.codec.binary.Base64; 

import ssi.peeno.managers.ConfigManager;
import ssi.peeno.managers.KeyManager;
import ssi.peeno.model.User;

public class AsymCipher implements PeenoCipher, AsymKeyGenerator {
	private RSAPublicKey publicKey;
	private RSAPrivateKey privateKey;
	private String userName;
	private Cipher cipher;
	private Base64 byteConverter;
	
	{
		init(); //cosi lo chiama sempre giusto?
		// mi sembra di si, ma se lo metti nel costruttore non è meglio?
		// oppure ti serve a ogni chiamata di metodo?
		//MC: serve per qualsiasi costruttore
	}
	
	public AsymCipher(String user) {
		this.userName = user;
		//MC serve veramente?
		// GC no...
	}
	
	public AsymCipher(User user) {
		publicKey = (RSAPublicKey) user.getPubKey();
		
	}
	
	private void init(){
		Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());
		byteConverter=new Base64();
		try {
			cipher = Cipher.getInstance("RSA/None/NoPadding", "BC");
		} catch (NoSuchAlgorithmException e) {
			e.printStackTrace();
		} catch (NoSuchProviderException e) {
			e.printStackTrace();
		} catch (NoSuchPaddingException e) {
			e.printStackTrace();
		}
	}
	
	/**
	 * Crypt the plaintext with key. 
	 * @param plaintext
	 * @param key
	 * @return
	 * @throws Exception
	 */
	@Override
	public String crypt(String plaintext, Key key) throws Exception {
		byte[] cipherTextBytes = null;
		cipher.init(Cipher.ENCRYPT_MODE, key);
		byte[] plaintextBytes=plaintext.getBytes("UTF-8");
		//split in block and crypt
		cipherTextBytes=splitAndDoIt(cipher, plaintextBytes);	
		String cipherText = byteConverter.encodeToString(cipherTextBytes);
		return cipherText;
	}
	
	public String cryptWithPublicKey(String plaintext) throws Exception{
		return crypt(plaintext, publicKey);
	}
	
	public String cryptWithPrivateKey(String plaintext) throws Exception{
		return crypt(plaintext, privateKey);
	}
	
	public String decrypt(String ciphertext, Key key) throws Exception {
		byte[] cipherTextBytes = byteConverter.decode(ciphertext);
		byte[] decryptedTextBytes = null;
		cipher.init(Cipher.DECRYPT_MODE, key); 
		//split in block and decrypt
		decryptedTextBytes=splitAndDoIt(cipher, cipherTextBytes);
		String decryptedText = new String(decryptedTextBytes, "UTF-8");
		return decryptedText;
	}

	public String decryptWithPrivate(String ciphertext) throws Exception {
		return decrypt(ciphertext, privateKey);
	}
	
	public String decryptWithPublic(String ciphertext)throws Exception {
		return decrypt(ciphertext, publicKey);
	}
	
	private byte[] splitAndDoIt(Cipher cipher, byte[] blocks)
			throws IOException {
		byte[] textBytes;
		ByteArrayOutputStream cipherTextBaos = new ByteArrayOutputStream();
		ByteArrayOutputStream baos;
		CipherOutputStream cos;
		int cipherTextLength = blocks.length;
		int blockSize = cipher.getBlockSize(); // get size of block I have to
		// split
		for (int bytesRead = 0; bytesRead < cipherTextLength; bytesRead = bytesRead
				+ blockSize) {
			if (bytesRead + blockSize > cipherTextLength) {
				// process and write in baos the processed byte LAST BLOCK
				baos = new ByteArrayOutputStream();
				cos = new CipherOutputStream(baos, cipher);
				cos.write(blocks, bytesRead, cipherTextLength - bytesRead);
				cos.close();
				cipherTextBaos.write(baos.toByteArray());
			} else {
				// process and write in baos the processed byte
				baos = new ByteArrayOutputStream();
				cos = new CipherOutputStream(baos, cipher);
				cos.write(blocks, bytesRead, blockSize);
				cos.close();
				cipherTextBaos.write(baos.toByteArray());
			}
		}
		cipherTextBaos.close();
		textBytes = cipherTextBaos.toByteArray();
		return textBytes;
	}

	/**
	 * Generate RSA public and private keys
	 */
	@Override
	public void generateKey() throws NoSuchAlgorithmException, NoSuchProviderException {
		KeyPairGenerator kpg = KeyPairGenerator.getInstance("RSA", "BC");
		kpg.initialize(1024);
		KeyPair keys = kpg.generateKeyPair();
		publicKey = (RSAPublicKey) keys.getPublic();
		privateKey = (RSAPrivateKey) keys.getPrivate();
	}

	@Override
	public Key getPrivateKey() {
		return privateKey;
	}

	@Override
	public Key getPublicKey() {
		return publicKey;
	}
	
	public void setPublicKey(Key publicKey) {
		this.publicKey = (RSAPublicKey) publicKey;
	}
	
	public void setPrivateKey(Key privateKey) {
		this.privateKey = (RSAPrivateKey) privateKey;
	}
	/**
	 * Get public key from string encoded form of public key
	 * @param keyString
	 * @return
	 * @throws Exception
	 */
	public static PublicKey getPubKeyFromString(String keyString) throws Exception{		
		Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());
		ConfigManager.log("" + keyString.length());
		Base64 b64 = new Base64();
		byte[] decodedKey = b64.decode(keyString);
		KeyFactory keyFactory = KeyFactory.getInstance("RSA", "BC");
		EncodedKeySpec publicKeySpec = new X509EncodedKeySpec(decodedKey);
		PublicKey publicKeyDecoded = (PublicKey) keyFactory.generatePublic(publicKeySpec);
		return publicKeyDecoded;
	}
	
	/**
	 * Load server keys from keystorage
	 */
	public void loadServerKeys(){
		KeyManager km = new KeyManager(ConfigManager.getKeysDir(), ConfigManager.getKeyPassword());
		publicKey=(RSAPublicKey) km.loadMyPublic();
		privateKey=(RSAPrivateKey) km.loadMyPrivate();
		
	}
}
